''' This module contains subroutines for converting an abstract tree to a Document Object Model (DOM).
'''

from xml.dom.minidom import Document

# Constants used for constructing a DOM
TAG_DIAGRAM         = 'diagram'
TAG_DIA_DECL        = 'restricted'
TAG_DIA_BODY        = 'diagram_body'
TAG_REPLICATOR      = 'replicator'
TAG_REPL_DECL       = 'names'
TAG_REPL_BODY       = 'body'
TAG_NAME            = 'name'
TAG_COMPARISON      = 'comparison'
TAG_PROCESS         = 'process'
TAG_CHILDREN        = 'children'
TAG_GUARD           = 'guard'
TAG_ACTION          = 'action'
ATTR_BOUND          = 'bound'
ATTR_NAME           = 'name'
ATTR_COMP_NAME_1    = 'first_name'
ATTR_COMP_NAME_2    = 'second_name'
ATTR_COMP_MACTH     = 'match'
ATTR_COMP_MMACTH    = 'mismatch'
ATTR_KIND           = 'kind'
ATTR_ACT_CHANNEL    = 'channel'
ATTR_ACT_INP        = 'input'
ATTR_ACT_OUT        = 'output'
ATTR_ACT_TAU        = 'tau'


class Converter(object):
    def __init__(self):
        pass
    
    
    def toDOM(self, ast):
        '''Convert an abstract syntax tree to DOM.
        '''
        self.initDOM()
        decl = None
        body = ast.getChildren()[0]
        if ast.numOfChildren() == 2:
            decl = ast.getChildren()[0]
            body = ast.getChildren()[1]
        # Collect restricted names
        if decl is not None:
            self.collectRestrictedNames(decl)
        # Compute all replicators and save them into DOM
        replicators = self.getElements(body)
        for repl in replicators:
            self.collectReplicator(repl)
        return self.doc
    
    
    def initDOM(self):
        self.doc = Document()
        root = self.doc.createElement(TAG_DIAGRAM)
        e = self.doc.createElement(TAG_DIA_DECL)
        root.appendChild(e)
        e = self.doc.createElement(TAG_DIA_BODY)
        root.appendChild(e)
        self.doc.appendChild(root)
    
        
    def collectRestrictedNames(self, root):
        '''Collect all restricted names.
        '''
        parent = self.doc.getElementsByTagName(TAG_DIA_DECL)[0]
        names = self.getElements(root)
        for name in names:
            self.createName(name, parent)
        
        
    def createName(self, name, parent=None):
        '''Create a node with tag 'name' and add to its parent.
        '''
        e = self.doc.createElement(TAG_NAME)
        e.setAttribute(ATTR_NAME, name)
        if parent is not None:
            parent.appendChild(e)
        return e
    
    
    def collectReplicator(self, repl_spec):
        '''Collect information of a replicator.
        '''
        decl = repl_spec.getChildren()[0]
        body = repl_spec.getChildren()[1]
        # Compute replicator name and bound
        bound = decl.getChildren()[0]
        r = self.doc.createElement(TAG_REPLICATOR)
        r.setAttribute(ATTR_NAME, bound.getChildren()[0])
        r.setAttribute(ATTR_BOUND, str(bound.getChildren()[1]))
        decl_node = self.doc.createElement(TAG_REPL_DECL)
        body_node = self.doc.createElement(TAG_REPL_BODY)
        r.appendChild(decl_node)
        r.appendChild(body_node)
        self.doc.getElementsByTagName(TAG_DIA_BODY)[0].appendChild(r)
        # Compute new names and add to DOM
        if decl.numOfChildren() == 2:
            names_decl = decl.getChildren()[1]
            names = self.getElements(names_decl)
            for name in names:
                self.createName(name, decl_node)
        # Compute processes and add to DOM
        processes = self.getElements(body)
        for proc in processes:
            self.createProcessNode(proc, body_node)
            
            
    def createProcessNode(self, process, parent):
        '''Collect information of a process (recursively).
        '''
        p = self.doc.createElement(TAG_PROCESS)
        ga = process.getChildren()[0]
        self.createGuard(ga.getChildren()[0], p)
        self.createAction(ga.getChildren()[1], p)
        c = self.doc.createElement(TAG_CHILDREN)
        p.appendChild(c)
        parent.appendChild(p)
        if process.numOfChildren() == 1:
            return p
        # create children nodes
        processes = self.getElements(process.getChildren()[1])
        for proc in processes:
            self.createProcessNode(proc, c)
            
            
    def createGuard(self, guard_spec, proc_node):
        ''' Create guards
        @note: order of comparisons are very important!
        '''
        g = self.doc.createElement(TAG_GUARD)
        proc_node.appendChild(g)
        comparisons = self.getElements(guard_spec)
        for c in comparisons:
            kind = c.getKind()
            if kind == 'empty_guard':
                continue
            first_name = c.getChildren()[0]
            second_name = c.getChildren()[1]
            m = self.doc.createElement(TAG_COMPARISON)
            m.setAttribute(ATTR_COMP_NAME_1, first_name)
            m.setAttribute(ATTR_COMP_NAME_2, second_name)
            if kind == 'match':
                m.setAttribute(ATTR_KIND, ATTR_COMP_MACTH)
            else:
                m.setAttribute(ATTR_KIND, ATTR_COMP_MMACTH)
            g.appendChild(m)
            
    
    def createAction(self, action_spec, proc_node):
        act = self.doc.createElement(TAG_ACTION)
        proc_node.appendChild(act)
        kind = action_spec.getKind()
        if kind == 'tau':
            act.setAttribute(ATTR_KIND, ATTR_ACT_TAU)
        else:
            channel = action_spec.getChildren()[0]
            name = action_spec.getChildren()[1]
            act.setAttribute(ATTR_ACT_CHANNEL, channel)
            act.setAttribute(ATTR_NAME, name)
            if kind == 'output':
                act.setAttribute(ATTR_KIND, ATTR_ACT_OUT)
            else:
                act.setAttribute(ATTR_KIND, ATTR_ACT_INP)
                

    def getElements(self, list_spec):
        '''Get elements of a list with the specification: 
                list : element | element list
        '''
        elements = []
        L = list_spec
        elements.append(L.getChildren()[0])
        while L.numOfChildren() == 2:
            L = L.getChildren()[1]
            elements.append(L.getChildren()[0])
        return elements
